/***************************************************************************
 创建者: 华磊
 开始时间: 2019.2.25
 copyright: (C) 华友高科
 修改说明: (每次有修改就添加一条，带有 修改人，修改时间，修改描述)
 example (1) 李文友 2017.5.8 类的具体实现代码编写                                                                       *
 ***************************************************************************/
#include "qrcodedetect.h"

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <QDebug>

using namespace cv;

QrcodeDetect::QrcodeDetect(double allowMinAreaIn,double allowMaxAreaIn,double allowMinLengthIn,
                           double allowMaxLengthIn, double colorThreholdIn)
{
    debugFlag=0;
    allowMinArea=allowMinAreaIn;
    allowMaxArea=allowMaxAreaIn;
    allowMinLength=allowMinLengthIn;
    allowMaxLength=allowMaxLengthIn;
    colorThrehold=colorThreholdIn;
    camera_width=1280;
    camera_height=720;
    camera_fps=15;
//    //---------初始化线程锁--------
//    pthread_mutexattr_init(&matchAttr);
//    pthread_mutexattr_setprotocol(&matchAttr,PTHREAD_PRIO_INHERIT);
//    pthread_mutex_init(&matchMutex,&matchAttr);

}

int QrcodeDetect::detectQrcode(int videoDeviceId, double &xOut, double &yOut, double &rOut, int &featureNumOut)
{
    Mat frame;
    if(false==deviceMap.contains(videoDeviceId))
    {
        VideoCapture *tmpDevice=new VideoCapture();
        if(false==tmpDevice->open(videoDeviceId))
        {
            return -100;
        }
        tmpDevice->set(CV_CAP_PROP_FPS,camera_fps);//max 30,
        tmpDevice->set(CV_CAP_PROP_FRAME_WIDTH,camera_width);//1920x1080, 1280x720
        tmpDevice->set(CV_CAP_PROP_FRAME_HEIGHT,camera_height);
        deviceMap.insert(videoDeviceId,tmpDevice);
    }
    //读取视频或摄像头,中途摄像头断线了没有报错。程序运行过程中，摄像头断线后重新连接，设备号会变。
    if(false==deviceMap.value(videoDeviceId)->isOpened())
    {
        return -100;
    }
//    (*deviceMap.value(videoDeviceId)) >> frame;//为什么感觉耗费了３ｓ

    try
    {
        if(false==deviceMap.value(videoDeviceId)->read(frame))
        {
            return -100;
        }
//        double rate = deviceMap.value(videoDeviceId)->get(CV_CAP_PROP_FPS);
//        int width=deviceMap.value(videoDeviceId)->get(CV_CAP_PROP_FRAME_WIDTH);
//        int height=deviceMap.value(videoDeviceId)->get(CV_CAP_PROP_FRAME_HEIGHT);
//        qDebug()<<"rate"<<rate<<" ,width"<<width<<" ,height"<<height;
    }
    catch (cv::Exception& e)
    {
//       char* msg_e = e.what();
       return -100;
    }

    if(!frame.empty())
    {
       int tmpKey= detectQrcode(frame,xOut,yOut,rOut,featureNumOut);
       xOut=xOut-camera_width/2;
       yOut=yOut-camera_height/2;
       return tmpKey;
    }
    return 0;
}

int QrcodeDetect::detectQrcode(QString filePath, double &xOut, double &yOut, double &rOut, int &featureNumOut)
{
      Mat src = imread( filePath.toStdString(), 1 );//不需要手动释放？
      return detectQrcode(src,xOut,yOut,rOut,featureNumOut);
}

int QrcodeDetect::detectQrcode(Mat srcImage, double &xOut, double &yOut, double &rOut, int &featureNumOut)
{
    //二维码的外框线细了点，加粗，提高鲁棒性。
    int returnFlag=0;
//    cv::resize(srcImage,srcImage,Size(800,600));//标准大小,不能修改大小，否则不清晰了
    Mat src_gray;
    Mat src_all=srcImage.clone();
    Mat threshold_output;
    vector<vector<Point> > contours,contours2;
    vector<Vec4i> hierarchy;
    //预处理
    cvtColor( srcImage, src_gray, CV_BGR2GRAY );
    if(1==debugFlag)
    {
        imshow( "灰度", src_gray );
    }

    blur( src_gray, src_gray, Size(2,2) ); //模糊，去除毛刺
    if(1==debugFlag)
    {
        imshow( "去毛刺", src_gray );;
    }
    //https://blog.csdn.net/u012566751/article/details/77046445 THRESH_OTSU为自动选择阀值
//    threshold( src_gray, threshold_output, colorThrehold, 255, THRESH_OTSU );//可以自适应灯光的影响。
    //这个识别稳定性比上面算法好。
    threshold( src_gray, threshold_output, colorThrehold, 255, THRESH_BINARY );//值越大代表白色。考虑灯光的影响。
    if(1==debugFlag)
    {
        imshow( "二值化", threshold_output );
    }
    //寻找轮廓
    //第一个参数是输入图像 2值化的
    //第二个参数是内存存储器，FindContours找到的轮廓放到内存里面。
    //第三个参数是层级，**[Next, Previous, First_Child, Parent]** 的vector
    //第四个参数是类型，采用树结构
    //第五个参数是节点拟合模式，这里是全部寻找
    findContours( threshold_output, contours, hierarchy,  CV_RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0) );
    //轮廓筛选
    int ic=0;
    int parentIdx=-1;

    //不允许二维码外部再被一个方框包裹
    for( int i = 0; i< contours.size(); i++ )
    {
        //hierarchy[i][2] != -1 表示不是最外面的轮廓
        if (hierarchy[i][2] != -1 && ic==0)
        {
            parentIdx = i;
            ic++;
        }
        else if (hierarchy[i][2] != -1)
        {
            ic++;
        }
        //最外面的清0
        else if(hierarchy[i][2] == -1)
        {
            ic = 0;
            parentIdx = -1;
        }
        //找到定位点信息
        if ( ic >= 2)
        {
            //判断方框大小是否符合要求
            double area = contourArea(contours[parentIdx]);
            if (area < allowMinArea || area >allowMaxArea)
            {
                continue;
            }
            //判断方框边长是否符合要求
            RotatedRect rect = minAreaRect(contours[parentIdx]);
            double w = rect.size.width;
            double h = rect.size.height;
            if (w < allowMinLength || w >allowMaxLength || h < allowMinLength || h >allowMaxLength )
            {
                continue;
            }

            contours2.push_back(contours[parentIdx]);
            ic = 0;
            parentIdx = -1;
        }
    }
    //填充定位点
    Point point[2];
    for(int i=0; i<contours2.size(); i++)
    {
        if(0==i)
        {
           point[0] = center_cal( contours2, 0 );
        }
        else
        {
            point[1] = center_cal( contours2, i );
            line(src_all,point[0],point[1],Scalar(0,0,255),2);
        }

        drawContours( src_all, contours2, i,  CV_RGB(0,255,0) , -1 );
    }

    QString showMsg;
    featureNumOut=contours2.size();
    if(3==contours2.size())
    {
        double tmpX,tmpY,tmpR;
        calculateQrcodeLocation(contours2,tmpX,tmpY,tmpR);
        showMsg="sucess x=";
        showMsg+=QString::number(tmpX,'g',4);
        showMsg+=" ,y="+QString::number(tmpY,'g',4);
        showMsg+=" ,r="+QString::number(tmpR*180.0/M_PI,'g',4);
        xOut=tmpX;
        yOut=tmpY;
        rOut=tmpR;
        returnFlag=1;
        putText(src_all,showMsg.toStdString(),Point(20,25),CV_FONT_HERSHEY_SIMPLEX,1,Scalar(100,100,0),2,2);
    }
    else
    {
        showMsg="failed ,features=";
        showMsg+=QString::number(contours2.size());
        returnFlag=-1;
        putText(src_all,showMsg.toStdString(),Point(20,25),CV_FONT_HERSHEY_SIMPLEX,1,Scalar(255,0,0),2,2);
    }


    if(1==debugFlag)
    {
        imshow( "二维码探测结果", src_all );
    }
    return returnFlag;

}

int QrcodeDetect::showDebugWindow(int id)
{
    debugFlag=id;
    return 1;
//    switch(id)
//    {
//    case 0:
//    {
//        if(0==pthread_mutex_trylock(&matchMutex))//
//        {
//            Mat tmpMat_baseMap=baseMap->clone();
//            Mat tmpMat_baseMap_originsource=baseMap_originSource->clone();
//            pthread_mutex_unlock(&matchMutex);
//            if( !tmpMat_baseMap.empty())
//            {
//                imshow("blackBackGround_basemap", tmpMat_baseMap);
//            }
//            lineMatch->showDebugWindow(0,tmpMat_baseMap_originsource);
//        }


//        break;
//    }
//    case 1:
//    {
//        if(0==pthread_mutex_trylock(&matchMutex))//
//        {
//            Mat tmpMat_localMap=localMap->clone();
//            Mat tmpMat_localCompareMap=localCompareMap->clone();
//            pthread_mutex_unlock(&matchMutex);
//            if( !tmpMat_localMap.empty())
//            {
//                line(tmpMat_localMap, Point(localMap_originX+0, localMap_originY+0), Point(localMap_originX+40, localMap_originY+0), Scalar(200, 255, 50), 2, 8);
//                line(tmpMat_localMap, Point(localMap_originX+0, localMap_originY+0), Point(localMap_originX+0, localMap_originY+80), Scalar(200, 255, 150), 2, 8);
//                imshow("blackBackGround_laser", tmpMat_localMap);
//            }
//            Mat three_channel;
//            if( !tmpMat_localCompareMap.empty() )
//            {

//                 three_channel= Mat::zeros(tmpMat_localCompareMap.rows,tmpMat_localCompareMap.cols,CV_8UC3);
//                 vector<Mat> channels;
//                 for (int i=0;i<3;i++)
//                 {
//                     channels.push_back(tmpMat_localCompareMap);
//                 }
//                 merge(channels,three_channel);
//                 line(three_channel, Point(localCompare_originX+0,localCompare_originY+ 0), Point(localCompare_originX+40,localCompare_originY+ 0), Scalar(200, 255, 50), 2, 8);
//                 line(three_channel, Point(localCompare_originX+0,localCompare_originY+ 0), Point(localCompare_originX+0, localCompare_originY+80), Scalar(200, 255, 150), 2, 8);
//                imshow("blackBackGround_laser_compare", three_channel);
//            }
//            lineMatch->showDebugWindow(0,three_channel);//hualei 没有加锁，可能导致ｃｏｒｅdump
//        }

//        break;
//    }
//    }
    //    return 1;
}

int QrcodeDetect::setDetectParameter(double allowMinAreaIn, double allowMaxAreaIn,
                           double allowMinLengthIn, double allowMaxLengthIn, double colorThreholdIn)
{
    qDebug()<<"QrcodeDetect::setDetectParameter before,allowMinArea "<<allowMinArea
           <<" ,allowMaxArea "<<allowMaxArea<<" ,allowMinLength "<<allowMinLength
             <<" ,allowMaxLength "<<allowMaxLength<<" ,colorThrehold "<<colorThrehold;
    allowMinArea=allowMinAreaIn;
    allowMaxArea=allowMaxAreaIn;
    allowMinLength=allowMinLengthIn;
    allowMaxLength=allowMaxLengthIn;
    colorThrehold=colorThreholdIn;

    qDebug()<<"QrcodeDetect::setDetectParameter after,allowMinArea "<<allowMinArea
           <<" ,allowMaxArea "<<allowMaxArea<<" ,allowMinLength "<<allowMinLength
             <<" ,allowMaxLength "<<allowMaxLength<<" ,colorThrehold "<<colorThrehold;
    return 1;
}

Point QrcodeDetect::center_cal(vector<vector<Point> > contours, int i)
{

    //找到所提取轮廓的中心点
    //在提取的中心小正方形的边界上每隔周长个像素提取一个点的坐标，求所提取四个点的平均坐标（即为小正方形的大致中心）

    int centerx=0,centery=0,n=contours[i].size();
    centerx = (contours[i][n/4].x + contours[i][n*2/4].x + contours[i][3*n/4].x + contours[i][n-1].x)/4;
    centery = (contours[i][n/4].y + contours[i][n*2/4].y + contours[i][3*n/4].y + contours[i][n-1].y)/4;
    Point point1=Point(centerx,centery);
    return point1;


}

Point2d QrcodeDetect::center_cal_pricise(vector<vector<Point> > contours, int i)
{

    //找到所提取轮廓的中心点
    //在提取的中心小正方形的边界上每隔周长个像素提取一个点的坐标，求所提取四个点的平均坐标（即为小正方形的大致中心）

    double centerx=0,centery=0,n=contours[i].size();
    double tmpSumX=0;
    double tmpSumY=0;
    for(int k=0;k<n;k++)
    {
        tmpSumX += contours[i][k].x;
        tmpSumY += contours[i][k].y;
    }
    centerx = tmpSumX/n;
    centery = tmpSumY/n;

    Point2d point1=Point2d(centerx,centery);
    return point1;


}

int QrcodeDetect::calculateQrcodeLocation(vector<vector<Point> > contours, double &xOut,
                                          double &yOut, double &rOut)
{
    if(3!=contours.size())
    {
        xOut=0;
        yOut=0;
        rOut=0;
        return -1;
    }
    Point2d tmpPoint1=center_cal_pricise(contours,0);
    Point2d tmpPoint2=center_cal_pricise(contours,1);
    Point2d tmpPoint3=center_cal_pricise(contours,2);
    //找出顶点,根据斜边距离最长
    double l1,l2,l3;
    l1=(tmpPoint2.y-tmpPoint3.y)*(tmpPoint2.y-tmpPoint3.y)+(tmpPoint2.x-tmpPoint3.x)*(tmpPoint2.x-tmpPoint3.x);
    l2=(tmpPoint1.y-tmpPoint3.y)*(tmpPoint1.y-tmpPoint3.y)+(tmpPoint1.x-tmpPoint3.x)*(tmpPoint1.x-tmpPoint3.x);
    l3=(tmpPoint1.y-tmpPoint2.y)*(tmpPoint1.y-tmpPoint2.y)+(tmpPoint1.x-tmpPoint2.x)*(tmpPoint1.x-tmpPoint2.x);
    double cornerX,cornerY,centerX,centerY;
    if(l1>l2 && l1>l3)
    {
        cornerX=tmpPoint1.x;
        cornerY=tmpPoint1.y;
        centerX=(tmpPoint2.x+tmpPoint3.x)/2;
        centerY=(tmpPoint2.y+tmpPoint3.y)/2;
    }
    else if(l2>l1 && l2>l3)
    {
        cornerX=tmpPoint2.x;
        cornerY=tmpPoint2.y;
        centerX=(tmpPoint1.x+tmpPoint3.x)/2;
        centerY=(tmpPoint1.y+tmpPoint3.y)/2;
    }
    else
    {
        cornerX=tmpPoint3.x;
        cornerY=tmpPoint3.y;
        centerX=(tmpPoint1.x+tmpPoint2.x)/2;
        centerY=(tmpPoint1.y+tmpPoint2.y)/2;
    }

    xOut=centerX;
    yOut=centerY;
    rOut=atan2(cornerY-centerY,cornerX-centerX);
    qDebug()<<"xOut="<<xOut<<"yOut="<<yOut<<"rOut="<<rOut*180/M_PI<<" cornerX"<<cornerX<<" cornerY"<<cornerY;
    return 1;

}

